# python cook book chapter one
import heapq
from collections import defaultdict, OrderedDict, deque, Counter, namedtuple, ChainMap
from operator import itemgetter, attrgetter
from itertools import groupby, compress
# 分割序列中的多个值 
def destructuring_assignment(): 
    k_v = [
        ('key1', 1, 2),
        ('key2', 3, 4)
    ]
    a, *b, c = (1, 2, 3, 4)
    d, *e, f = (1, 2) # *可以分割多个值赋值给变量，即使没有，也是一个空list
    print(a, b, c, d, e, f)

    for key, *val in k_v:
        print(key, val)

# 获取最大的三个元素 
def get_largest_3(data, key): 
    return heapq.nlargest(3, data, key=lambda good:good[key]) # 第三个参数为函数，数组元素作为第一个函数参数

# 保存历史记录
def search(lines, patten, history=5):
    previous_lines = deque(maxlen = history) # deque(maxlen=N) 创建固定长度的队列，如果length超过N，一端添加一个元素，另一端即相应的删除一个元素
    for line in lines:
        if patten in line:
            yield line, previous_lines  # yield 生成器函数,程序暂停，返回yield后面表达式的值，在下一次调用的时候，从yield语句暂停的地方继续执行，如此循环，直到函数执行完。
            previous_lines.append(line)  

# if __name__ == '__main__':
#     with open('demo.txt') as f:
#         for line, prevlines in search(f, 'python', 2):
#             for pline in prevlines:
#                 print(pline, end="")
#             print('-'*20)

# yield关键字 
# 第一次调用必须调用__next__(),从函数起点到yield关键字结束，再次调用从上一次yield结束处到下一个yield处或者生成器结束，
# sent()函数只能从yield之后开始，到下一个yield结束。这也就意味着第一次调用必须使用__next__函数。
# sent(arg)函数参数可以给yield赋值语句赋值，它的返回值是yield表达式的值

def fun():
    for i in range(20):
        x = yield i
        print('good',x)

if __name__ == '__main__':
    a = fun()
    a.__next__()
    x = a.send(5)
    print(x)

# 优先级队列
class PriorityQueue:
    def __init__(self):
        self._queue = []
        self._index = 0
    
    def push(self, item, priority):
        heapq.heappush(self._queue, (-priority, self._index, item)) # -priority为了将最大优先级第一个导出, index为了区分优先级相同情况，先存储优先级更高 
        self._index += 1

    def pop(self):
        return heapq.heappop(self._queue)[-1]

# 创建一键多值的字典

d = defaultdict(list) # 多个值存储在list中，即使键名不存在，也会有一个默认的空list作为值，也可以使用普通dict的setdefault方法
d['a'].append(1)

# 创建一个有序的字典
orderedD = OrderedDict() # 对其迭代时，会严格按照元素添加顺序，占用内存是普通字典两倍左右，存储大量数据时，需衡量占用内存和使用收益之间谁更重要
orderedD['b'] = 1
orderedD['a'] = 2
orderedD['c'] = 3

# zip函数反转字典的键值对
prices = {
    'ACME': 45.23,
    'AAPL': 612.78,
    'IBM': 205.55,
    'HPQ': 37.55,
    'FB': 10.55
}

minKeyVal = min(zip(prices.values(), prices.keys())) # result: (10.55, 'FB'),zip函数会将多个可迭代对象的对应元素分别打包成一个元祖,可以很方面的获得最大最小值对应的键

# 去除序列重复项且保持元素顺序不变
def dedupe(items, key=None):
    seen = set()
    for item in items:
        val = item if key is None else key(item) # 通过制定参数key来实现对不可哈希对象的处理
        if val not in seen:
            yield item
            seen.add(val)

# 找出序列中出现次数最多元素
words = [
    1, 1, 1,
    2, 2, 2, 2, 2,
    3, 3, 3, 3, 3,
    4, 4, 4, 4, 4, 4, 4,
    5
]

words_counts = Counter(words) # 底层实现为一个字典，对每个元素出现次数作了映射
top_three = words_counts.most_common(3)
print(top_three)
more_words = [1, 2, 3]
for word in more_words:
    words_counts[word] += 1 # 手动自增
# words_counts.update(more_words) 或者通过update方法更新
print(words_counts.most_common(3))
# Counter不同对象支持数学运算操作
a = Counter([1, 2, 3, 3])
b = Counter([1, 3])
print(a - b)
print(a + b)

# 通过公共键对字典列表排序
rows = [
    { 'fname': 'Bill', 'lname': 'Jones', 'uid': 1004 },
    { 'fname': 'David', 'lname': 'Beazley', 'uid': 1002 },
    { 'fname': 'John', 'lname': 'Cleese', 'uid': 1002 },
    { 'fname': 'Big', 'lname': 'Jones', 'uid': 1004 }
]

rows_by_fname = sorted(rows, key=itemgetter('fname'))
rows_by_lfname = sorted(rows, key=itemgetter('lname', 'fname')) # 根据多个键排序
# 等同于 两者区别：itemgetter运行更快，性能更优
rows_by_fname_lambda = sorted(rows, key=lambda r: r['fname'])
rows_by_lfname_lambda = sorted(rows, key=lambda r: (r['lname'], r['fname']))
print(rows_by_fname)
print(rows_by_fname_lambda)

'''
使用它attrgetter对不原生支持比较操作的对象排序
attrgetter使用方式和itemgetter使用方式类似，但是用于获取对象属性
'''

# 根据字段将记录分组
rows.sort(key = itemgetter('uid')) # 注意点：使用groupby排序前需先对原来列表进行排序，使分组记录达到连续
for uid, items in groupby(rows, key=itemgetter('uid')): # groupby返回一个迭代器，每次迭代返回一个元组，第一个元素是排序项的值，第二个是一个子迭代器，产生该分组内的所有项
    print(uid)
    for item in items:
        print(item)

# 另一种思路，创建一个一键多值的字典（该方案不需要对列表事先进行排序，节省内存）
rows_by_uid = defaultdict(list)
for row in rows:
    rows_by_uid[row['uid']].append(row)

# 筛选列表中的数据
mylist = [1, 4, -5, 10, -7, 2, 3, -1]
print([n for n in mylist if n > 0]) # 列表推导式
list_iterator = (n for n in mylist if n > 0)
for x in list_iterator: # 迭代器方式，适用于庞大输入
    print(x)

# 生成器方案
values = ['1', '2', '-3', '-', 'N/A', '5']

def is_int(val):
    try:
        x = int(val)
        return True
    except ValueError:
        return False

ivals = list(filter(is_int, values)) # filter函数返回一个迭代器
print(ivals)

# compress函数筛选
address = [
    1, 2, 3, 4
]
bool_list = [
    True, False, False, True
]
print(list(compress(address, bool_list))) # 第二个参数为一个布尔序列，为True则返回，compress函数也返回一个迭代器

# 从字典中提取子集
prices = {
    'ACME': 45.23,
    'AAPL': 345.23,
    'IBM': 245.23,
    'HPQ': 145.23,
    'FB': 25.23
}

p1 = { key: value for key, value in prices.items() if value > 200 } # 此方案效率最快
print(p1)

# 给序列元素增加名称映射
Subscriber = namedtuple('Subscriber', ['attr', 'joined'])
sub = Subscriber('jonesy@example.com', '2012-01-01') # 不等同于普通类实例，支持元组的操作
print(sub.attr, sub[0]) # namedtuple可作为字典的替代，但是后者需要更多的空间来存储，且namedtuple是不可变的，改变属性需要通过_replace方法来实现

#### 多个映射合并为单个映射
a = { 'x': 1, 'z': 3 }
b = { 'y': 1, 'z': 4 }
c = ChainMap(a, b) # 重复的键会采用第一个映射中的值，对映射的操作总是会作用于第一个映射
print(c) # 也可以通过字典的update方法将多个字典合并，但是需要重新构建一个新的字典，且对任意一个原始字典作了修改都不会返回到合并后的字典中去